package gredis

import (
	"errors"
	"fmt"
	"gonexample/gone/config"
	"time"

	"github.com/gomodule/redigo/redis"
)

const (
	// 限速器脚本 redis lua
	SpeedLimiterRedisLuaScript = `
local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
	redis.call("expire",KEYS[1],ARGV[1])
end
`
)

var (
	// DefaultKey the collection name of redis for cache adapter.
	DefaultKey = config.Redis.Prefix

	RedisConn *redis.Pool
)

// Redis Connect
func Initialize() {
	dialFunc := func() (c redis.Conn, err error) {
		c, err = redis.Dial("tcp", config.Redis.Host)
		if err != nil {
			return nil, err
		}

		if config.Redis.Password != "" {
			if _, err := c.Do("AUTH", config.Redis.Password); err != nil {
				c.Close()
				return nil, err
			}
		}

		_, selecterr := c.Do("SELECT", config.Redis.DbNum)
		if selecterr != nil {
			c.Close()
			return nil, selecterr
		}
		return
	}

	testOnBorrow := func(c redis.Conn, t time.Time) error {
		_, err := c.Do("PING")
		return err
	}
	// initialize a new pool
	RedisConn = &redis.Pool{
		MaxIdle:      config.Redis.MaxIdle,
		IdleTimeout:  60 * time.Second,
		Dial:         dialFunc,
		TestOnBorrow: testOnBorrow,
	}
}

// actually do the redis cmds, args[0] must be the key name.
func Do(commandName string, args ...interface{}) (reply interface{}, err error) {
	if len(args) < 1 {
		return nil, errors.New("missing required arguments")
	}
	args[0] = associate(args[0])

	c := RedisConn.Get()
	defer c.Close()

	return c.Do(commandName, args...)
}

// associate with config key.
func associate(originKey interface{}) string {
	return fmt.Sprintf("%s:%s", DefaultKey, originKey)
}

// Get cache from redis.
func Get(key string) interface{} {
	if v, err := Do("GET", key); err == nil {
		return v
	}
	return nil
}

// GetMulti get cache from redis.
func GetMulti(keys []string) []interface{} {
	c := RedisConn.Get()
	defer c.Close()

	var args []interface{}
	for _, key := range keys {
		args = append(args, associate(key))
	}
	values, err := redis.Values(c.Do("MGET", args...))
	if err != nil {
		return nil
	}
	return values
}

// Put put cache to redis.
func Put(key string, val interface{}, timeout time.Duration) error {
	_, err := Do("SETEX", key, int64(timeout/time.Second), val)
	return err
}

// Delete delete cache in redis.
func Delete(key string) error {
	_, err := Do("DEL", key)
	return err
}

// 获取 keys 缓存剩余时间
func TTL(key string) int {
	if v, err := redis.Int(Do("TTL", key)); err == nil {
		return v
	}
	return 0
}

// IsExist check cache's existence in redis.
func IsExist(key string) bool {
	v, err := redis.Bool(Do("EXISTS", key))
	if err != nil {
		return false
	}
	return v
}

// Incr increase counter in redis.
func Incr(key string) error {
	_, err := redis.Bool(Do("INCRBY", key, 1))
	return err
}

// Decr decrease counter in redis.
func Decr(key string) error {
	_, err := redis.Bool(Do("INCRBY", key, -1))
	return err
}

// ClearAll clean all cache in redis. delete this redis collection.
func ClearAll() error {
	c := RedisConn.Get()
	defer c.Close()

	cachedKeys, err := redis.Strings(c.Do("KEYS", DefaultKey+":*"))
	if err != nil {
		return err
	}
	for _, str := range cachedKeys {
		if _, err = c.Do("DEL", str); err != nil {
			return err
		}
	}
	return err
}

// 接口调用频率限制方法
func Interrupt(key string, expire int) bool {
	if Get(key) != nil {
		return true
	}

	Put(key, 1, time.Duration(expire)*time.Second)

	return false
}

// 限速器
func SpeedLimit(key string, max, expire int) bool {
	if num, _ := redis.Int(Get(key), nil); num >= max {
		return true
	}

	conn, _ := RedisConn.Dial()
	redis.NewScript(1, SpeedLimiterRedisLuaScript).Do(conn, associate(key), expire)

	return false
}
